#include "../../../include/common/json/writer.h"
#include <utility>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <sstream>
#include <iomanip>

#if _MSC_VER >= 1400            // VC++ 8.0
#pragma warning(disable : 4996) // disable warning about strdup being deprecated.
#endif

namespace Json
{

static bool isControlCharacter(char ch)
{
  return ch > 0 && ch <= 0x1F;
}

static bool containsControlCharacter(const char *str)
{
  while (*str)
  {
    if (isControlCharacter(*(str++)))
      return true;
  }
  return false;
}
static void uintToString(unsigned int value,
                         char *&current)
{
  *--current = 0;
  do
  {
    *--current = (value % 10) + '0';
    value /= 10;
  } while (value != 0);
}

std::string valueToString(Int value)
{
  char buffer[32];
  char *current = buffer + sizeof(buffer);
  bool isNegative = value < 0;
  if (isNegative)
    value = -value;
  uintToString(UInt(value), current);
  if (isNegative)
    *--current = '-';
  assert(current >= buffer);
  return current;
}

std::string valueToString(UInt value)
{
  char buffer[32];
  char *current = buffer + sizeof(buffer);
  uintToString(value, current);
  assert(current >= buffer);
  return current;
}

std::string valueToString(double value)
{
  char buffer[32];
#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning.
  sprintf_s(buffer, sizeof(buffer), "%#.16g", value);
#else
  sprintf(buffer, "%#.16g", value);
#endif
  char *ch = buffer + strlen(buffer) - 1;
  if (*ch != '0')
    return buffer; // nothing to truncate, so save time
  while (ch > buffer && *ch == '0')
  {
    --ch;
  }
  char *last_nonzero = ch;
  while (ch >= buffer)
  {
    switch (*ch)
    {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      --ch;
      continue;
    case '.':
      // Truncate zeroes to save bytes in output, but keep one.
      *(last_nonzero + 2) = '\0';
      return buffer;
    default:
      return buffer;
    }
  }
  return buffer;
}

std::string valueToString(bool value)
{
  return value ? "true" : "false";
}

std::string valueToQuotedString(const char *value)
{
  // Not sure how to handle unicode...
  if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && !containsControlCharacter(value))
    return std::string("\"") + value + "\"";
  // We have to walk value and escape any special characters.
  // Appending to std::string is not efficient, but this should be rare.
  // (Note: forward slashes are *not* rare, but I am not escaping them.)
  unsigned maxsize = strlen(value) * 2 + 3; // allescaped+quotes+NULL
  std::string result;
  result.reserve(maxsize); // to avoid lots of mallocs
  result += "\"";
  for (const char *c = value; *c != 0; ++c)
  {
    switch (*c)
    {
    case '\"':
      result += "\\\"";
      break;
    case '\\':
      result += "\\\\";
      break;
    case '\b':
      result += "\\b";
      break;
    case '\f':
      result += "\\f";
      break;
    case '\n':
      result += "\\n";
      break;
    case '\r':
      result += "\\r";
      break;
    case '\t':
      result += "\\t";
      break;
    //case '/':
    // Even though \/ is considered a legal escape in JSON, a bare
    // slash is also legal, so I see no reason to escape it.
    // (I hope I am not misunderstanding something.
    // blep notes: actually escaping \/ may be useful in javascript to avoid </
    // sequence.
    // Should add a flag to allow this compatibility mode and prevent this
    // sequence from occurring.
    default:
      if (isControlCharacter(*c))
      {
        std::ostringstream oss;
        oss << "\\u" << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << static_cast<int>(*c);
        result += oss.str();
      }
      else
      {
        result += *c;
      }
      break;
    }
  }
  result += "\"";
  return result;
}

// Class Writer
// //////////////////////////////////////////////////////////////////
Writer::~Writer()
{
}

// Class FastWriter
// //////////////////////////////////////////////////////////////////

FastWriter::FastWriter()
    : yamlCompatiblityEnabled_(false)
{
}

void FastWriter::enableYAMLCompatibility()
{
  yamlCompatiblityEnabled_ = true;
}

std::string
FastWriter::write(const Value &root)
{
  document_ = "";
  writeValue(root);
  document_ += "\n";
  return document_;
}

void FastWriter::writeValue(const Value &value)
{
  switch (value.type())
  {
  case nullValue:
    document_ += "null";
    break;
  case intValue:
    document_ += valueToString(value.asInt());
    break;
  case uintValue:
    document_ += valueToString(value.asUInt());
    break;
  case realValue:
    document_ += valueToString(value.asDouble());
    break;
  case stringValue:
    document_ += valueToQuotedString(value.asCString());
    break;
  case booleanValue:
    document_ += valueToString(value.asBool());
    break;
  case arrayValue:
  {
    document_ += "[";
    int size = value.size();
    for (int index = 0; index < size; ++index)
    {
      if (index > 0)
        document_ += ",";
      writeValue(value[index]);
    }
    document_ += "]";
  }
  break;
  case objectValue:
  {
    Value::Members members(value.getMemberNames());
    document_ += "{";
    for (Value::Members::iterator it = members.begin();
         it != members.end();
         ++it)
    {
      const std::string &name = *it;
      if (it != members.begin())
        document_ += ",";
      document_ += valueToQuotedString(name.c_str());
      document_ += yamlCompatiblityEnabled_ ? ": "
                                            : ":";
      writeValue(value[name]);
    }
    document_ += "}";
  }
  break;
  }
}

// Class StyledWriter
// //////////////////////////////////////////////////////////////////

StyledWriter::StyledWriter()
    : rightMargin_(74), indentSize_(3)
{
}

std::string
StyledWriter::write(const Value &root)
{
  document_ = "";
  addChildValues_ = false;
  indentString_ = "";
  writeCommentBeforeValue(root);
  writeValue(root);
  writeCommentAfterValueOnSameLine(root);
  document_ += "\n";
  return document_;
}

void StyledWriter::writeValue(const Value &value)
{
  switch (value.type())
  {
  case nullValue:
    pushValue("null");
    break;
  case intValue:
    pushValue(valueToString(value.asInt()));
    break;
  case uintValue:
    pushValue(valueToString(value.asUInt()));
    break;
  case realValue:
    pushValue(valueToString(value.asDouble()));
    break;
  case stringValue:
    pushValue(valueToQuotedString(value.asCString()));
    break;
  case booleanValue:
    pushValue(valueToString(value.asBool()));
    break;
  case arrayValue:
    writeArrayValue(value);
    break;
  case objectValue:
  {
    Value::Members members(value.getMemberNames());
    if (members.empty())
      pushValue("{}");
    else
    {
      writeWithIndent("{");
      indent();
      Value::Members::iterator it = members.begin();
      while (true)
      {
        const std::string &name = *it;
        const Value &childValue = value[name];
        writeCommentBeforeValue(childValue);
        writeWithIndent(valueToQuotedString(name.c_str()));
        document_ += " : ";
        writeValue(childValue);
        if (++it == members.end())
        {
          writeCommentAfterValueOnSameLine(childValue);
          break;
        }
        document_ += ",";
        writeCommentAfterValueOnSameLine(childValue);
      }
      unindent();
      writeWithIndent("}");
    }
  }
  break;
  }
}

void StyledWriter::writeArrayValue(const Value &value)
{
  unsigned size = value.size();
  if (size == 0)
    pushValue("[]");
  else
  {
    bool isArrayMultiLine = isMultineArray(value);
    if (isArrayMultiLine)
    {
      writeWithIndent("[");
      indent();
      bool hasChildValue = !childValues_.empty();
      unsigned index = 0;
      while (true)
      {
        const Value &childValue = value[index];
        writeCommentBeforeValue(childValue);
        if (hasChildValue)
          writeWithIndent(childValues_[index]);
        else
        {
          writeIndent();
          writeValue(childValue);
        }
        if (++index == size)
        {
          writeCommentAfterValueOnSameLine(childValue);
          break;
        }
        document_ += ",";
        writeCommentAfterValueOnSameLine(childValue);
      }
      unindent();
      writeWithIndent("]");
    }
    else // output on a single line
    {
      assert(childValues_.size() == size);
      document_ += "[ ";
      for (unsigned index = 0; index < size; ++index)
      {
        if (index > 0)
          document_ += ", ";
        document_ += childValues_[index];
      }
      document_ += " ]";
    }
  }
}

bool StyledWriter::isMultineArray(const Value &value)
{
  int size = value.size();
  bool isMultiLine = size * 3 >= rightMargin_;
  childValues_.clear();
  for (int index = 0; index < size && !isMultiLine; ++index)
  {
    const Value &childValue = value[index];
    isMultiLine = isMultiLine ||
                  ((childValue.isArray() || childValue.isObject()) &&
                   childValue.size() > 0);
  }
  if (!isMultiLine) // check if line length > max line length
  {
    childValues_.reserve(size);
    addChildValues_ = true;
    int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
    for (int index = 0; index < size && !isMultiLine; ++index)
    {
      writeValue(value[index]);
      lineLength += int(childValues_[index].length());
      isMultiLine = isMultiLine && hasCommentForValue(value[index]);
    }
    addChildValues_ = false;
    isMultiLine = isMultiLine || lineLength >= rightMargin_;
  }
  return isMultiLine;
}

void StyledWriter::pushValue(const std::string &value)
{
  if (addChildValues_)
    childValues_.push_back(value);
  else
    document_ += value;
}

void StyledWriter::writeIndent()
{
  if (!document_.empty())
  {
    char last = document_[document_.length() - 1];
    if (last == ' ') // already indented
      return;
    if (last != '\n') // Comments may add new-line
      document_ += '\n';
  }
  document_ += indentString_;
}

void StyledWriter::writeWithIndent(const std::string &value)
{
  writeIndent();
  document_ += value;
}

void StyledWriter::indent()
{
  indentString_ += std::string(indentSize_, ' ');
}

void StyledWriter::unindent()
{
  assert(int(indentString_.size()) >= indentSize_);
  indentString_.resize(indentString_.size() - indentSize_);
}

void StyledWriter::writeCommentBeforeValue(const Value &root)
{
  if (!root.hasComment(commentBefore))
    return;
  document_ += normalizeEOL(root.getComment(commentBefore));
  document_ += "\n";
}

void StyledWriter::writeCommentAfterValueOnSameLine(const Value &root)
{
  if (root.hasComment(commentAfterOnSameLine))
    document_ += " " + normalizeEOL(root.getComment(commentAfterOnSameLine));

  if (root.hasComment(commentAfter))
  {
    document_ += "\n";
    document_ += normalizeEOL(root.getComment(commentAfter));
    document_ += "\n";
  }
}

bool StyledWriter::hasCommentForValue(const Value &value)
{
  return value.hasComment(commentBefore) || value.hasComment(commentAfterOnSameLine) || value.hasComment(commentAfter);
}

std::string
StyledWriter::normalizeEOL(const std::string &text)
{
  std::string normalized;
  normalized.reserve(text.length());
  const char *begin = text.c_str();
  const char *end = begin + text.length();
  const char *current = begin;
  while (current != end)
  {
    char c = *current++;
    if (c == '\r') // mac or dos EOL
    {
      if (*current == '\n') // convert dos EOL
        ++current;
      normalized += '\n';
    }
    else // handle unix EOL & other char
      normalized += c;
  }
  return normalized;
}

// Class StyledStreamWriter
// //////////////////////////////////////////////////////////////////

StyledStreamWriter::StyledStreamWriter(std::string indentation)
    : document_(NULL), rightMargin_(74), indentation_(indentation)
{
}

void StyledStreamWriter::write(std::ostream &out, const Value &root)
{
  document_ = &out;
  addChildValues_ = false;
  indentString_ = "";
  writeCommentBeforeValue(root);
  writeValue(root);
  writeCommentAfterValueOnSameLine(root);
  *document_ << "\n";
  document_ = NULL; // Forget the stream, for safety.
}

void StyledStreamWriter::writeValue(const Value &value)
{
  switch (value.type())
  {
  case nullValue:
    pushValue("null");
    break;
  case intValue:
    pushValue(valueToString(value.asInt()));
    break;
  case uintValue:
    pushValue(valueToString(value.asUInt()));
    break;
  case realValue:
    pushValue(valueToString(value.asDouble()));
    break;
  case stringValue:
    pushValue(valueToQuotedString(value.asCString()));
    break;
  case booleanValue:
    pushValue(valueToString(value.asBool()));
    break;
  case arrayValue:
    writeArrayValue(value);
    break;
  case objectValue:
  {
    Value::Members members(value.getMemberNames());
    if (members.empty())
      pushValue("{}");
    else
    {
      writeWithIndent("{");
      indent();
      Value::Members::iterator it = members.begin();
      while (true)
      {
        const std::string &name = *it;
        const Value &childValue = value[name];
        writeCommentBeforeValue(childValue);
        writeWithIndent(valueToQuotedString(name.c_str()));
        *document_ << " : ";
        writeValue(childValue);
        if (++it == members.end())
        {
          writeCommentAfterValueOnSameLine(childValue);
          break;
        }
        *document_ << ",";
        writeCommentAfterValueOnSameLine(childValue);
      }
      unindent();
      writeWithIndent("}");
    }
  }
  break;
  }
}

void StyledStreamWriter::writeArrayValue(const Value &value)
{
  unsigned size = value.size();
  if (size == 0)
    pushValue("[]");
  else
  {
    bool isArrayMultiLine = isMultineArray(value);
    if (isArrayMultiLine)
    {
      writeWithIndent("[");
      indent();
      bool hasChildValue = !childValues_.empty();
      unsigned index = 0;
      while (true)
      {
        const Value &childValue = value[index];
        writeCommentBeforeValue(childValue);
        if (hasChildValue)
          writeWithIndent(childValues_[index]);
        else
        {
          writeIndent();
          writeValue(childValue);
        }
        if (++index == size)
        {
          writeCommentAfterValueOnSameLine(childValue);
          break;
        }
        *document_ << ",";
        writeCommentAfterValueOnSameLine(childValue);
      }
      unindent();
      writeWithIndent("]");
    }
    else // output on a single line
    {
      assert(childValues_.size() == size);
      *document_ << "[ ";
      for (unsigned index = 0; index < size; ++index)
      {
        if (index > 0)
          *document_ << ", ";
        *document_ << childValues_[index];
      }
      *document_ << " ]";
    }
  }
}

bool StyledStreamWriter::isMultineArray(const Value &value)
{
  int size = value.size();
  bool isMultiLine = size * 3 >= rightMargin_;
  childValues_.clear();
  for (int index = 0; index < size && !isMultiLine; ++index)
  {
    const Value &childValue = value[index];
    isMultiLine = isMultiLine ||
                  ((childValue.isArray() || childValue.isObject()) &&
                   childValue.size() > 0);
  }
  if (!isMultiLine) // check if line length > max line length
  {
    childValues_.reserve(size);
    addChildValues_ = true;
    int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
    for (int index = 0; index < size && !isMultiLine; ++index)
    {
      writeValue(value[index]);
      lineLength += int(childValues_[index].length());
      isMultiLine = isMultiLine && hasCommentForValue(value[index]);
    }
    addChildValues_ = false;
    isMultiLine = isMultiLine || lineLength >= rightMargin_;
  }
  return isMultiLine;
}

void StyledStreamWriter::pushValue(const std::string &value)
{
  if (addChildValues_)
    childValues_.push_back(value);
  else
    *document_ << value;
}

void StyledStreamWriter::writeIndent()
{
  /*
    Some comments in this method would have been nice. ;-)

   if ( !document_.empty() )
   {
      char last = document_[document_.length()-1];
      if ( last == ' ' )     // already indented
         return;
      if ( last != '\n' )    // Comments may add new-line
         *document_ << '\n';
   }
  */
  *document_ << '\n'
             << indentString_;
}

void StyledStreamWriter::writeWithIndent(const std::string &value)
{
  writeIndent();
  *document_ << value;
}

void StyledStreamWriter::indent()
{
  indentString_ += indentation_;
}

void StyledStreamWriter::unindent()
{
  assert(indentString_.size() >= indentation_.size());
  indentString_.resize(indentString_.size() - indentation_.size());
}

void StyledStreamWriter::writeCommentBeforeValue(const Value &root)
{
  if (!root.hasComment(commentBefore))
    return;
  *document_ << normalizeEOL(root.getComment(commentBefore));
  *document_ << "\n";
}

void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value &root)
{
  if (root.hasComment(commentAfterOnSameLine))
    *document_ << " " + normalizeEOL(root.getComment(commentAfterOnSameLine));

  if (root.hasComment(commentAfter))
  {
    *document_ << "\n";
    *document_ << normalizeEOL(root.getComment(commentAfter));
    *document_ << "\n";
  }
}

bool StyledStreamWriter::hasCommentForValue(const Value &value)
{
  return value.hasComment(commentBefore) || value.hasComment(commentAfterOnSameLine) || value.hasComment(commentAfter);
}

std::string
StyledStreamWriter::normalizeEOL(const std::string &text)
{
  std::string normalized;
  normalized.reserve(text.length());
  const char *begin = text.c_str();
  const char *end = begin + text.length();
  const char *current = begin;
  while (current != end)
  {
    char c = *current++;
    if (c == '\r') // mac or dos EOL
    {
      if (*current == '\n') // convert dos EOL
        ++current;
      normalized += '\n';
    }
    else // handle unix EOL & other char
      normalized += c;
  }
  return normalized;
}

std::ostream &operator<<(std::ostream &sout, const Value &root)
{
  Json::StyledStreamWriter writer;
  writer.write(sout, root);
  return sout;
}

} // namespace Json
